﻿using System;
using System.Threading;
using Microsoft.DirectX.DirectSound;

namespace MicReader
{
    public class Mic
    {
        public enum MicState
        {
            NoDevices,
            Idle,
            Receiving
        };

        public MicState State { get; set; }

        private CaptureDevicesCollection CaptureDevs;
        public int MicIndex { get; set; }
        private Capture CaptureMic = null;
        private WaveFormat WaveFormatCurrent;
        private Notify myNotify = null;
        private int iNotifyNum = 16;
        private int iBufferOffset = 0;
        public int SampleCount { get; private set; }
        private int iNotifySize = 1024;
        private int iBufferSize = 16384;
        private CaptureBuffer CaptureBufferCurrent = null;
        private AutoResetEvent NotifyEvent = null;
        private Thread NotifyThread = null;

        public int SamplesPerSecond { get; set; }
        public short BitsPerSample { get; set; }
        public short Channels { get; set; }

        private Frame[] CurrentFrame;
        private int FrameBits = 9;

        public Mic()
        {
            State = MicState.NoDevices;
            SamplesPerSecond = 44100;
            BitsPerSample = 16;
            Channels = 1;
        }

        private int FindDevices()
        {
            if (State != MicState.Receiving)
            {
                CaptureDevs = new CaptureDevicesCollection();

                if (CaptureDevs.Count > 0)
                {
                    MicIndex = 0;
                    State = MicState.Idle;
                }
                else
                {
                    State = MicState.NoDevices;
                }
            }
            return (CaptureDevs.Count);
        }

        private void SetWaveFormat()
        {
            WaveFormatCurrent = new WaveFormat();

            WaveFormatCurrent.FormatTag = WaveFormatTag.Pcm;
            WaveFormatCurrent.SamplesPerSecond = SamplesPerSecond;
            WaveFormatCurrent.BitsPerSample = BitsPerSample;
            WaveFormatCurrent.Channels = Channels;
            WaveFormatCurrent.BlockAlign = (short)(WaveFormatCurrent.Channels * (WaveFormatCurrent.BitsPerSample / 8));
            WaveFormatCurrent.AverageBytesPerSecond = WaveFormatCurrent.BlockAlign * WaveFormatCurrent.SamplesPerSecond;
        }

        private void CreateCaptureBuffer()
        {
            CaptureBufferDescription BufferDescription = new CaptureBufferDescription();
            CurrentFrame = new Frame[Channels];

            for (int i = 0; i < Channels; i++)
            {
                CurrentFrame[i] = new Frame(BitsPerSample, FrameBits);
            }
            BufferDescription.Format = WaveFormatCurrent;
            iBufferSize = iNotifyNum * iNotifySize;
            BufferDescription.BufferBytes = iBufferSize;
            CaptureBufferCurrent = new CaptureBuffer(BufferDescription, CaptureMic);
        }

        private void CreateNotification()
        {
            BufferPositionNotify[] BPN = new BufferPositionNotify[iNotifyNum];

            NotifyEvent = new AutoResetEvent(false);
            NotifyThread = new Thread(Capture);
            NotifyThread.Start();
            for (int i = 0; i < iNotifyNum; i++)
            {
                BPN[i].Offset = iNotifySize + i * iNotifySize - 1;
                BPN[i].EventNotifyHandle = NotifyEvent.Handle;
            }
            myNotify = new Notify(CaptureBufferCurrent);
            myNotify.SetNotificationPositions(BPN);
        }

        private void Capture()
        {
            while (true)
            {
                NotifyEvent.WaitOne(Timeout.Infinite, true);
                GetCapturedData();
            }
        }

        private void GetCapturedData()
        {
            byte[] CaptureData = null;
            int ReadPos = 0, CapturePos = 0, LockSize = 0;
            int BytesPerSample = WaveFormatCurrent.BitsPerSample / 8;
            int SampleValue;

            CaptureBufferCurrent.GetCurrentPosition(out CapturePos, out ReadPos);
            LockSize = ReadPos - iBufferOffset;
            if (LockSize == 0)
            {
                return;
            }
            if (LockSize < 0)
            {
                LockSize += iBufferSize;
            }

            if (LockSize % WaveFormatCurrent.BlockAlign == 0)
            {
                CaptureData = (byte[])CaptureBufferCurrent.Read(iBufferOffset, typeof(byte), LockFlag.FromWriteCursor, LockSize);
                for (int i = 0; i < LockSize; i += WaveFormatCurrent.BlockAlign)
                {
                    for (int j = 0; j < WaveFormatCurrent.Channels; j++)
                    {
                        if (BytesPerSample == 1)
                        {
                            SampleValue = CaptureData[i + j * BytesPerSample];
                        }
                        else
                        {
                            SampleValue = BitConverter.ToInt16(CaptureData, i + j * BytesPerSample);
                        }
                        CurrentFrame[j].Add(SampleValue);
                    }
                }
            }

            SampleCount += CaptureData.Length;
            iBufferOffset += CaptureData.Length;
            iBufferOffset %= iBufferSize;
        }

        public bool Start()
        {
            if (State == MicState.NoDevices)
            {
                if (FindDevices() <= 0)
                {
                    return (false);
                }
                else
                {
                    State = MicState.Idle;
                }
            }
            CaptureMic = new Capture(CaptureDevs[MicIndex].DriverGuid);

            SetWaveFormat();
            CreateCaptureBuffer();
            CreateNotification();
            CaptureBufferCurrent.Start(true);
            State = MicState.Receiving;
            return (true);
        }

        public void End()
        {
            if (State == MicState.Receiving)
            {
                if (CaptureBufferCurrent != null)
                {
                    CaptureBufferCurrent.Stop();
                    if (NotifyEvent != null)
                    {
                        NotifyEvent.Set();
                    }
                    if (NotifyThread != null)
                    {
                        NotifyThread.Abort();
                    }
                    GetCapturedData();
                }
                State = MicState.Idle;
            }
            iBufferOffset = 0;
            SampleCount = 0;
        }

        public String[] GetDeviceName()
        {
            String[] DevicesName;

            if (State == MicState.NoDevices)
            {
                FindDevices();
            }
            if (State == MicState.Idle || State == MicState.Receiving)
            {
                DevicesName = new String[CaptureDevs.Count];

                for (int i = 0; i < CaptureDevs.Count; i++)
                {
                    DevicesName[i] = CaptureDevs[i].Description;
                }
                return (DevicesName);
            }
            else
            {
                return (null);
            }
        }

        public String GetDeviceName(int No)
        {
            if (State == MicState.NoDevices)
            {
                FindDevices();
            }
            if (State == MicState.Idle || State == MicState.Receiving)
            {
                return (CaptureDevs[No].Description);
            }
            else
            {
                return (null);
            }
        }

        public bool SetDevice(int No)
        {
            if (State == MicState.Idle && CaptureDevs.Count > No)
            {
                MicIndex = No;
                return (true);
            }
            else
            {
                return (false);
            }
        }

        public int CurrentSample(int Channel)
        {
            if (State == MicState.Receiving)
            {
                return (CurrentFrame[Channel].CurrentSample);
            }
            else
            {
                return (0);
            }
        }

        public double CurrentdB(int Channel)
        {
            if (State == MicState.Receiving)
            {
                return (CurrentFrame[Channel].dB);
            }
            else
            {
                return (0.0);
            }
        }

        public double[] CurrentFreqMagnitude(int Channel)
        {
            if (State == MicState.Receiving)
            {
                return (CurrentFrame[Channel].Magnitude());
            }
            else
            {
                return (null);
            }
        }

        public int FrameSize(int Channel)
        {
            return (CurrentFrame[Channel].Size);
        }
    }

    public class Frame
    {
        public int Size { get; private set; }
        private int FrameBits;

        private int[] Samples;
        private int SamplesPtr = -1;
        private short BitsPerSample = 16;
        private double SampleRange;
        
        private int[] ReverseBitTable;
        private double[] ComplexSamples;

        public Frame(short BPS, int Bits)
        {
            FrameBits = Bits;
            Size = (int)Math.Pow(2.0, (double)FrameBits);
            Samples = new int[Size];
            BitsPerSample = BPS;
            SampleRange = Math.Pow(2.0, (double)(BitsPerSample - 1));
            ReverseBitTable = BuildRBTable();
            ComplexSamples = new double[Size * 2];
        }

        public void Add(int Value)
        {
            SamplesPtr++;
            if (SamplesPtr == Size)
            {
                SamplesPtr = 0;
            }
            Samples[SamplesPtr] = Value;
            if (BitsPerSample == 8)
            {
                Samples[SamplesPtr] -= 128;
            }
        }

        public int CurrentSample
        {
            get
            {
                if (SamplesPtr != -1)
                {
                    return (Samples[SamplesPtr]);
                }
                else
                {
                    return (0);
                }
            }
        }

        public double dB
        {
            get
            {
                double Volume = 0.0;
                double RealSample;

                for (int i = 0; i < Size; i++)
                {
                    RealSample = Samples[i] / SampleRange;
                    Volume += RealSample * RealSample;
                }
                Volume = 10.0 * Math.Log10(Volume);

                return (Volume);
            }
        }

        public double[] Magnitude()
        {
            double[] Magnitude = new double[Size];

            for (int i = 0; i < Size; i++)
            {
                ComplexSamples[i * 2] = Samples[i] / SampleRange;
                ComplexSamples[i * 2 + 1] = 0.0;
            }
            FFT(1);
            for (int i = 0; i < Size; i++)
            {
                Magnitude[i] = Math.Sqrt(ComplexSamples[i * 2] * ComplexSamples[i * 2]
                    + ComplexSamples[i * 2 + 1] * ComplexSamples[i * 2 + 1]);
            }

            return (Magnitude);
        }

        private int[] BuildRBTable()
        {
            int[] Table = new int[Size];

            for (int i = 0; i < Size; i++)
            {
                Table[i] = ReverseBits(i, FrameBits) * 2;
            }

            return (Table);
        }

        private int ReverseBits(int Index, int Bits)
        {
            int Value = 0;

            for (int i = 0; i < Bits; i++)
            {
                Value = (Value << 1) | (Index & 1);
                Index >>= 1;
            }

            return (Value);
        }

        private void FFT(int sign)
        {
            int n, mmax, m, istep;
            double wtemp, wr, wpr, wpi, wi, theta, tempr, tempi;
            int i, j;
            double Temp;

            n = Size * 2;

            // Bits-Reversal
            for (i = 0; i < n / 2; i += 2)
            {
                if (ReverseBitTable[i] > i)
                {
                    Temp = ComplexSamples[ReverseBitTable[i]];
                    ComplexSamples[ReverseBitTable[i]] = ComplexSamples[i];
                    ComplexSamples[i] = Temp;

                    Temp = ComplexSamples[ReverseBitTable[i] + 1];
                    ComplexSamples[ReverseBitTable[i] + 1] = ComplexSamples[i + 1];
                    ComplexSamples[i + 1] = Temp;
                }
            }

            // Danielson-Lanzcos
            mmax = 2;

            //external loop
            while (n > mmax)
            {
                istep = mmax << 1;
                theta = sign * (2 * Math.PI / mmax);
                wtemp = Math.Sin(0.5 * theta);
                wpr = -2.0 * wtemp * wtemp;
                wpi = Math.Sin(theta);
                wr = 1.0;
                wi = 0.0;

                //internal loops
                for (m = 1; m < mmax; m += 2)
                {
                    for (i = m; i <= n; i += istep)
                    {
                        j = i + mmax;
                        tempr = wr * ComplexSamples[j - 1] - wi * ComplexSamples[j];
                        tempi = wr * ComplexSamples[j] + wi * ComplexSamples[j - 1];
                        ComplexSamples[j - 1] = ComplexSamples[i - 1] - tempr;
                        ComplexSamples[j] = ComplexSamples[i] - tempi;
                        ComplexSamples[i - 1] += tempr;
                        ComplexSamples[i] += tempi;
                    }
                    wr = (wtemp = wr) * wpr - wi * wpi + wr;
                    wi = wi * wpr + wtemp * wpi + wi;
                }
                mmax = istep;
            }
        }
    }
}
